package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.message.PointsMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * @author 小海
 */
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;

    /**
     * 签到
     *
     * @return
     */
    @Override
    public SignResultVO addSignRecords() {
        // 1. 签到，操作bitmap		setbit 202306:1 offset 1
        // 1.1 获得当前登录的用户
        Long userId = UserContext.getUser();
        // 1.2 获得当前的日期
        LocalDate now = LocalDate.now();
        // 1.3 拼接key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + ":" + now.format(DateTimeFormatter.ofPattern("yyyyMM"));
        // 1.4 计算offset
        int offset = now.getDayOfMonth() - 1;
        // 1.5 保存
        Boolean result = redisTemplate.opsForValue().setBit(key, offset, true);
        // 如果是true证明之前已经签到过了，现在不能重复签到
        if (BooleanUtils.isTrue(result)) {
            throw new BizIllegalException("不能重复签到");
        }
        // 2.统计连续签到天数
        int signDays = countSignDays(key, now.getDayOfMonth());

        // 3.积分功能
        int rewardPoints = 0;
        switch (signDays) {
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 30;
                break;
        }

        // 4.发送积分给MQ
        mqHelper.send(MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                PointsMessage.of(userId, rewardPoints + 1)
        );
        // 5.封装vo
        SignResultVO vo = new SignResultVO();
        vo.setRewardPoints(rewardPoints);
        vo.setSignDays(signDays);
        return vo;
    }

    // 统计连续签到天数
    private int countSignDays(String key, int dayOfMonth) {
        // 2.1 执行bitfield 获得截止到今天的签到情况	BITFIELD key GET u[dayOfMonth] 0
        List<Long> result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        // 2.2 取出结果   10进制直接转2进制
        int num = result.get(0).intValue();
        // 2.3 循环，与1做与运算，得到最后一个bit，判断是否为0，为0则终止，为1则继续
        int count = 0;
        while ((num & 1) == 1) {
            count++;
            num >>>= 1;
        }
        return count;
    }

    @Override
    public Byte[] querySignRecords() {
        // 1.获取当前用户
        Long userId = UserContext.getUser();
        // 2.获取当前日期
        LocalDate now = LocalDate.now();
        int dayOfMonth = now.getDayOfMonth();
        // 3.拼接key
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + ":" + now.format(DateTimeFormatter.ofPattern("yyyyMM"));
        // 4.执行bitfield，获取结果 BITFIELD key GET u[dayOfMonth] 0
        List<Long> result = redisTemplate.opsForValue().bitField(
                key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return new Byte[0];
        }
        int num = result.get(0).intValue();
        Byte[] bytes = new Byte[dayOfMonth];
        // 数组的下标
        int pos = dayOfMonth - 1;
        while (pos >= 0) {
            // 从num取出位的值，进行填充
            bytes[pos] = (byte) (num & 1);
            num >>>= 1;
            pos--;
        }
        // 5.循环，填充Byte[]数组
        return bytes;
    }
}
